/**
 * 
 */
package algo;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import porte.Client;
import porte.Porte;
import serveur_reso.Reso;

/**
 * @author BDRANY Akeel
 * @author BAYRAMOV Matin
 *
 */
public class AlgoJeton extends UnicastRemoteObject implements Client, Algorithm {

	private Porte porte;
	
	private int idPorte;
	
	private Jeton jeton;
	
	// Identifiant de porte qu'on pense qu'il a le jeton
	private int owner;
	
	private int successeur;
	
	private boolean sectionCritique;  

	private int k;
	
	private Serializable message;
	
	private String requete;
	
	private PrintWriter trace;

	private static final long serialVersionUID = 1L;
	
	
	private enum TYPE_MESSAGE {
		REQUETE_ENTREE,
		REQUETE_SORTIR,
		DONNE_JETON,
		ACCEPT_MESSAGE
	}

	
	public AlgoJeton(Porte porte) throws RemoteException {
		super();

		this.setPorte(porte);
		this.idPorte = porte.getId();

		this.jeton = new Jeton(nombreDePlaceLibre);
		this.initalisation();
		this.requete = "DEMANDE";
		
		try {
			trace  = new PrintWriter(new FileWriter("trace_jeton_Porte_" + idPorte));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		trace("avec "+this.jeton);
		trace("owner "+this.owner);
	}
	
	public void trace(String string) {
		trace.print(string);
		trace.flush();
	}

	
	public void initalisation() {
		
		successeur = -1;
		owner = 0;
		this.jeton.setJeton(false);
		sectionCritique = false;
		
		// Premiere processus (porte) possède le jeton
		if (idPorte == 0) {
			this.jeton.setJeton(true);
			owner = -1;
		} 
	}

	public synchronized void demandeAcces(TYPE_MESSAGE typeMessage) throws RemoteException,
			InterruptedException {
		trace("==> Le porte demande entrer dans Section Critique ");
		
		sectionCritique = true;
	
		if (owner != -1) {
			
			this.jeton.typeMessage = TYPE_MESSAGE.DONNE_JETON;
			this.jeton.idDemandeurSC = this.idPorte;
			
			trace("Envoie demande de "+idPorte+ " à " + owner );
			this.getReso().sendMessage(idPorte, owner, this.jeton);

			owner = -1;
			
			while (!this.jeton.isJeton()) {
				trace("Le porte est dans l'attente le jeton");
				this.wait();				
			}
		}
		
		
		// Entree de section critique 

		trace("**************Start SectionCrtique()************************\n");
		traiteDemandeEntreeSortie(typeMessage);
		wait(3000);
		sortieSectionCritique(this.idPorte);
		trace("***************Fin SectionCrtique()************************\n\n\n");
	
		
	}
	
	
	public synchronized void traiteDemandeEntreeSortie(TYPE_MESSAGE typeMessage)  {
		
		if (typeMessage == TYPE_MESSAGE.REQUETE_ENTREE) {
			
			if (jeton.nombreDePlaceLibre == 0) {
				trace("==>Le porte a refusee la demande d'entree \n");	
				return;
			}
			
			if (jeton.nombreDePlaceLibre > 0) {
				jeton.nombreDePlaceLibre--;
				trace("==>Le porte a accepte la demande d'entree\n");
				return;
			}			
		}

		if (typeMessage == TYPE_MESSAGE.REQUETE_SORTIR) {
			jeton.nombreDePlaceLibre++;
			trace("==>Le porte a accepte la demande de sortir\n");
		}
	}
	  

	public synchronized void acceptMessage(int id, Jeton jetonUpdate)
			throws RemoteException, InterruptedException {
		
		trace("Porte"+ this.idPorte +"==>  acceptMessage de "+ id);
		this.jeton.idDemandeurSC = jetonUpdate.idDemandeurSC;
		
		if (owner == -1) {
			
			if (sectionCritique) {
				successeur = id;				
			} 
			else {
				
				trace("Porte"+this.idPorte+"==> Envoie jeton à " + this.jeton.idDemandeurSC + " avec : "+this.jeton+"\n");
				
				trace("\n******************Fin************************\n\n");
				this.jeton.typeMessage = TYPE_MESSAGE.DONNE_JETON;
				this.getReso().sendMessage(this.idPorte, this.jeton.idDemandeurSC, this.jeton);
				this.jeton.setJeton(false);
				
			}
		}
		else {
			
			this.getReso().sendMessage(idPorte, owner, this.jeton);
		}
		
		owner = this.jeton.idDemandeurSC;
	

		
	}

	public synchronized void acceptJeton(Jeton newJeton) {
		
		trace("Porte"+this.idPorte+"==> Accept le Jeton avec nombreDePlaceLibre ==> "+newJeton.nombreDePlaceLibre);
		this.jeton = newJeton;
		notify();
		trace("\n*********************Fin************************\n\n");
	}

	
	public synchronized void sortieSectionCritique(int id) {
		sectionCritique = false;
		if (successeur != -1) {		
			
			try {
				
				// Envoie le jeton à mon successeur
				this.getReso().sendMessage(idPorte, successeur, this.jeton);
				
				this.jeton.setJeton(false);
				successeur = -1;
				
			} catch (RemoteException e) {
				e.printStackTrace();
			}
			
		}
	}

	@Override
	public synchronized void receiveMessage(int from, int to, Serializable msg)
			throws RemoteException {
		trace("\n****************** Start recoit message **************************\n");
		trace("\nPorte"+to+ "==> recoit message depuis "+from);
		Jeton jeton = (Jeton) msg;
		
		if (jeton.isJeton()) {
			this.acceptJeton(jeton);
			return;
		}
		
		try {
			this.acceptMessage(from, (Jeton) msg);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void demandeEntree() throws RemoteException {

		trace("\n*********************************************\n");
		trace("\n==> PORTE "+this.idPorte + " demande Entree\n");
		trace("avec "+this.jeton);
		trace("owner "+this.owner);
		trace("\n*********************************************\n");
		
		try {
			this.demandeAcces(TYPE_MESSAGE.REQUETE_ENTREE);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void demandeSortie() throws RemoteException {
		
		trace("\n*********************************************\n");
		trace("PORTE "+this.idPorte + " demande Sortir");
		trace("avec "+this.jeton);
		trace("owner "+this.owner);
		trace("\n*********************************************\n");
		
		try {
			this.demandeAcces(TYPE_MESSAGE.REQUETE_SORTIR);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @return the porte
	 */
	public Porte getPorte() {
		return porte;
	}

	/**
	 * @param porte
	 *            the porte to set
	 */
	public void setPorte(Porte porte) {
		this.porte = porte;
	}

	/**
	 * @return the msg
	 */
	public Serializable getMsg() {
		return message;
	}

	/**
	 * @param msg the msg to set
	 */
	public void setMsg(Serializable msg) {
		this.message = msg;
	}

	/**
	 * @return the k
	 */
	public int getK() {
		return k;
	}

	/**
	 * @param k the k to set
	 */
	public void setK(int k) {
		this.k = k;
	}
	
	/**
	 * 
	 * @return
	 */
	public Reso getReso() {
		return this.getPorte().getReso();
	}
	
	
	/**
	 * 
	 * @author b
	 *
	 */
	private static class Jeton implements java.io.Serializable {

		private static final long serialVersionUID = 1L;
		
		private int nombreDePlaceLibre;
		
		private boolean isJeton;
		
		private int idDemandeurSC;
		
		private TYPE_MESSAGE typeMessage;

		public Jeton(int nbPlaceLibre) {
			this.setTypeMessage(TYPE_MESSAGE.ACCEPT_MESSAGE);
			
			this.setNombreDePlaceLibre(nbPlaceLibre);
		}

		/**
		 * @return the nombreDePlaceLibre
		 */
		public int getNombreDePlaceLibre() {
			return nombreDePlaceLibre;
		}

		/**
		 * @param nombreDePlaceLibre the nombreDePlaceLibre to set
		 */
		public void setNombreDePlaceLibre(int nombreDePlaceLibre) {
			this.nombreDePlaceLibre = nombreDePlaceLibre;
		}

		/**
		 * @return the typeMessage
		 */
		public TYPE_MESSAGE getTypeMessage() {
			return typeMessage;
		}

		/**
		 * @param typeMessage the typeMessage to set
		 */
		public void setTypeMessage(TYPE_MESSAGE typeMessage) {
			this.typeMessage = typeMessage;
		}

		/**
		 * 
		 * @return
		 */
		public boolean isJeton() {
			return isJeton;
		}

		/**
		 * 
		 * @param isJeton
		 */
		public void setJeton(boolean isJeton) {
			this.isJeton = isJeton;
		}
		
		/**
		 * 
		 */
		public String toString() {
			
			StringBuilder string = new StringBuilder();
			string.append("j(");
			string.append(this.getNombreDePlaceLibre()+", ");
			string.append(this.getTypeMessage()+") ");
			string.append("idDemandeurSC: "+this.idDemandeurSC + ", ");
			string.append(this.isJeton());
			
			return string.toString();
		}
	}
	
	
	public String toString() {
		
		StringBuilder string = new StringBuilder();
		string.append("[");
		string.append(this.idPorte+", ");
		string.append(this.successeur+"next ");
		//string.append(this.);
		
		return string.toString();
	}


	@Override
	public void initialise() throws RemoteException {
		// TODO Auto-generated method stub
		
	}

}
